﻿/*******************************************************************************
 * Copyright (c) 2007
 * Sergey Berezin, Dmitry Voitsekhovskiy
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms,
 * with or without modification, are permitted provided
 * that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * - Neither the name of the Moscow State University nor the names
 * of its contributors may be used to endorse or promote products
 * derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 ******************************************************************************/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;

namespace Faml.Operations
{
    [OperationContainer]
    public sealed class GenericApply
    {
        [OperationName("apply")]
        public static R[] Apply<T,R>(Expression<Func<T, T, R>> expr, T[] a, T[] b)
        {
            Func<T, T, R> func = (Func<T, T, R>)expr.Compile();

            int len = a.Length;
            R[] value = new R[len];

            for (int i = 0; i < len; i++)
            {
                value[i] = func(a[i], b[i]);
            }

            return value;
        }

        [OperationName("apply")]
        public static R[] Apply<T, R>(Expression<Func<T, T, T, T, R>> expr, T[] a, T[] b, T[] c, T[] d)
        {
            Func<T, T, T, T, R> func = (Func<T, T, T, T, R>)expr.Compile();

            int len = a.Length;
            R[] value = new R[len];

            for (int i = 0; i < len; i++)
            {
                value[i] = func(a[i], b[i], c[i], d[i]);
            }

            return value;
        }

        [OperationName("apply")]
        public static R[] Apply<T, R>(Expression<Func<T, R>> expr, T[] a)
        {
            Func<T, R> func = (Func<T, R>)expr.Compile();

            int len = a.Length;
            R[] value = new R[len];

            for (int i = 0; i < len; i++)
            {
                value[i] = func(a[i]);
            }

            return value;
        }

        [OperationName("apply")]
        public static R[,] Apply<T, R>(Expression<Func<T, R>> expr, T[,] a)
        {
            Func<T, R> func = (Func<T, R>)expr.Compile();

            int w = a.GetLength(0),
                h = a.GetLength(1);
            R[,] value = new R[w, h];

            for (int i = 0; i < w; i++)
            {
                for (int j = 0; j < h; j++)
                {
                    value[i, j] = func(a[i, j]);
                }
            }

            return value;
        }

        [OperationName("apply")]
        public static R[,] Apply<T, R>(Expression<Func<T, T, R>> expr, T[,] a, T[,] b)
        {
            Func<T, T, R> func = (Func<T, T, R>)expr.Compile();

            int w = a.GetLength(0),
                h = a.GetLength(1);
            R[,] value = new R[w, h];

            for (int i = 0; i < w; i++)
            {
                for (int j = 0; j < h; j++)
                {
                    value[i,j] = func(a[i,j], b[i,j]);                    
                }
            }

            return value;
        }

        [OperationName("apply")]
        public static R[,,] Apply<T, R>(Expression<Func<T, T, R>> expr, T[,,] a, T[,,] b)
        {
            Func<T, T, R> func = (Func<T, T, R>)expr.Compile();

            int w = a.GetLength(0),
                h = a.GetLength(1),
                d = a.GetLength(2);

            R[,,] value = new R[w, h, d];

            for (int i = 0; i < w; i++)
            {
                for (int j = 0; j < h; j++)
                {
                    for (int k = 0; k < d; k++)
                    {
                        value[i, j, k] = func(a[i, j, k], b[i, j, k]);
                    }
                }
            }

            return value;
        }

        [OperationName("apply")]
        public static T[] Apply<T>(Expression<FuncParams<T, T>> expr, params T[][] arrays)
        {
            if (arrays.Length == 0)
                throw new ApplicationException("apply requires at least one argument");

            FuncParams<T, T> func = (FuncParams<T, T>)expr.Compile();

            int len = arrays[0].Length;
            T[] value = new T[len];

            if (arrays.Length == 1)
                for (int i = 0; i < len; i++)
                {
                    value[i] = func(arrays[0][i]);
                }
            else if (arrays.Length == 2)
                for (int i = 0; i < len; i++)
                {
                    value[i] = func(arrays[0][i], arrays[1][i]);
                }
            else if (arrays.Length == 3)
                for (int i = 0; i < len; i++)
                {
                    value[i] = func(arrays[0][i], arrays[1][i], arrays[2][i]);
                }
            else if (arrays.Length == 4)
                for (int i = 0; i < len; i++)
                {
                    value[i] = func(arrays[0][i], arrays[1][i], arrays[2][i], arrays[3][i]);
                }
            else if (arrays.Length == 5)
                for (int i = 0; i < len; i++)
                {
                    value[i] = func(arrays[0][i], arrays[1][i], arrays[2][i], arrays[3][i], arrays[4][i]);
                }
            else
            {
                T[] args = new T[arrays.Length];
                for (int i = 0; i < len; i++)
                {
                    for (int j = 0; j < args.Length; j++)
                    {
                        args[j] = arrays[i][j];
                    }
                    value[i] = func(args);
                }
            }

            return value;
        }


        [OperationName("apply")]
        public static T[,] Apply<T>(Expression<FuncParams<T, T>> expr, params T[][,] arrays)
        {
            int w = arrays[0].GetLength(0),
                h = arrays[0].GetLength(1);

            FuncParams<T, T> func = (FuncParams<T, T>)expr.Compile();

            T[,] value = new T[w, h];

            if (arrays.Length == 1)
                for (int i = 0; i < w; i++)
                    for (int j = 0; j < h; j++)
                    {
                        value[i, j] = func(arrays[0][i, j]);
                    }
            else if (arrays.Length == 2)
                for (int i = 0; i < w; i++)
                    for (int j = 0; j < h; j++)
                    {
                        value[i, j] = func(arrays[0][i, j], arrays[1][i, j]);
                    }
            else if (arrays.Length == 3)
                for (int i = 0; i < w; i++)
                    for (int j = 0; j < h; j++)
                    {
                        value[i, j] = func(arrays[0][i, j], arrays[1][i, j], arrays[2][i, j]);
                    }
            else if (arrays.Length == 4)
                for (int i = 0; i < w; i++)
                    for (int j = 0; j < h; j++)
                    {
                        value[i, j] = func(arrays[0][i, j], arrays[1][i, j], arrays[2][i, j], arrays[3][i, j]);
                    }
            else if (arrays.Length == 5)
                for (int i = 0; i < w; i++)
                    for (int j = 0; j < h; j++)
                    {
                        value[i, j] = func(arrays[0][i, j], arrays[1][i, j], arrays[2][i, j], arrays[3][i, j], arrays[4][i, j]);
                    }
            else
            {
                T[] args = new T[arrays.Length];
                for (int i = 0; i < w; i++)
                    for (int j = 0; j < h; j++)
                    {
                        for (int k = 0; k < args.Length; k++)
                        {
                            args[j] = arrays[k][i, j];
                        }
                        value[i, j] = func(args);
                    }
            }

            return value;
        }
    }

}
